﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Camera;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using Utils;


namespace Model
{
    [Serializable]
    public class World
    {
        private CameraObject _me;
        [NonSerialized]
        private const double _gravityMultiplier = .1;
        [NonSerialized]
        private double _currentGravityMomentum;

        /// <summary>
        /// Bool that enables or disables Bounding Box drawing on the geometry
        /// </summary>
        [NonSerialized]
        public bool DrawBoundingBox;

        public event EventHandler<CameraEventArgs> UpdateCamera;

        /// <summary>
        /// Enables or disabled collision.  Also turns on and off Gravity
        /// </summary>
        public bool Collision;

        /// <summary>
        /// Gets or sets the geometry for the world.
        /// </summary>
        /// <value>
        /// The geometry.
        /// </value>
        public readonly List<Surface> Geometry;

        /// <summary>
        /// Gets or sets the various camera objects.
        /// </summary>
        /// <value>
        /// The people.
        /// </value>
        public readonly List<CameraObject> People;

        /// <summary>
        /// Gets or sets the lights.
        /// </summary>
        /// <value>
        /// The lights.
        /// </value>
        public readonly List<Light> Lights;

        /// <summary>
        /// Gets or sets the meshes objects.
        /// </summary>
        /// <value>
        /// The meshes.
        /// </value>
        public readonly List<MeshData> Meshes;

        public readonly List<Vector3d[]> Particles;

        public double MeX { get { return _me.Position.X; } }
        public double MeY { get { return _me.Position.Y; } }
        public double MeZ { get { return _me.Position.Z; } }

        public World()
        {
            _me = new CameraObject();
            Geometry = new List<Surface>();
            People = new List<CameraObject>();
            Lights = new List<Light>(8);
            Meshes = new List<MeshData>();
            Particles = new List<Vector3d[]>();
        }

        public void SetStart(Point3D pos)
        {
            _me.Position = pos;
        }

        public void ResetMe()
        {
            People.Add(_me);
            _me = new CameraObject();
        }

        /// <summary>
        /// Draws the world.
        /// 
        /// Handles all of the lights, geometry, cameras, and meshes.
        /// This will handle the Light Naming, Fog, shading, etc.
        /// 
        /// This is also where Gravity will be applied.
        /// </summary>
        public void DrawWorld()
        {
            float x, y, z;
            SoundUtils.getListenerLocation(out x, out y, out z);
            float deltax = x - (float)_me.Position.X;
            float deltay = y - (float)_me.Position.Y;
            float deltaz = z - (float)_me.Position.Z;

            SoundUtils.setListenerLocation(_me.Position, _me.Azimuth, _me.Angle, deltax, deltay, deltaz);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            GL.ClearColor(Color4.Gray);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            GL.ShadeModel(ShadingModel.Smooth);
            //GL.Enable(EnableCap.Normalize);
            GL.Enable(EnableCap.Lighting);
            GL.Enable(EnableCap.ColorMaterial);
            //GL.ColorMaterial(MaterialFace.FrontAndBack, ColorMaterialParameter.AmbientAndDiffuse);
            //GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, new Color4(.4f, .4f, .4f, .4f));
            //GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Emission, new Color4(0f, 0f, 0f, 1f));

            GL.LightModel(LightModelParameter.LightModelAmbient, new[] { 0.0f, 0.0f, 0.0f, 1.0f });

            var light = new Light { AmbientColor = new Color4(0, 0, 0, 0), DiffuseColor = new Color4(.8f, .8f, .8f, 1f), Position = new Point3D(0, 5, 0), Positional = true };
            light.AddLight(LightName.Light0, EnableCap.Light0);

            var mat = _me.GetCameraMatrix();
            GL.LoadMatrix(ref mat);

            var rand = new Random();
            for (int i = 0; i < Lights.Count; i++)
            {
                Lights[i].AddLight(LightName.Light1 + i, EnableCap.Light1 + i);
                if (Lights[i].Particle)
                {
                    int mult = rand.Next(-1, 1);
                    var pos = new Vector3d(Lights[i].Position.X, Lights[i].Position.Y, Lights[i].Position.Z);
                    var ang = new Vector3d(rand.NextDouble() * mult, rand.NextDouble() * mult, rand.NextDouble() * mult);
                    Particles.Add(new Vector3d[] { pos, ang });
                }
            }

            foreach (var cube in Geometry)
            {
                cube.DrawSurface();
            }

            foreach (var mesh in Meshes)
            {
                mesh.DrawMesh();
                if (DrawBoundingBox)
                    mesh.Box.DrawBox();
            }

            GL.Color4(Color4.White);
            GL.Begin(BeginMode.Points);
            for (int i = 0; i < Particles.Count; i++)
            {
                while (i < Particles.Count && Particles[i][0].Y < -1.1)
                    Particles.Remove(Particles[i]);

                if (i >= Particles.Count)
                    break;

                GL.Vertex3(Particles[i][0].X, Particles[i][0].Y, Particles[i][0].Z);

                var pos = new Vector3d(Particles[i][0].X + Particles[i][1].X, Particles[i][0].Y + Particles[i][1].Y, Particles[i][0].Z + Particles[i][1].Z);
                var ang = new Vector3d(Particles[i][1].X, Particles[i][1].Y - .01, Particles[i][1].Z);

                Particles[i] = new Vector3d[] { pos, ang };
            }
            GL.End();



            lock (People)
            {
                foreach (var cameraObject in People)
                {
                    cameraObject.DrawCamera();
                }
            }

            GL.Disable(EnableCap.Lighting);

            ApplyGravity();
        }

        /// <summary>
        /// Applies gravity to the "_me" camera object.
        /// 
        /// If Collision is false, it will not apply any gravity.
        /// 
        /// </summary>
        private void ApplyGravity()
        {
            if (!Collision) return;

            var box = _me.Box;

            if (_currentGravityMomentum == 0)
                _currentGravityMomentum += _gravityMultiplier;
            else
                _currentGravityMomentum *= 2;

            var test = box.Center.Y;
            box.Center.Y -= _currentGravityMomentum;

            foreach (var serface in Geometry)
            {
                foreach (var wall in serface.Walls)
                {
                    wall.Box.Intersection(ref box);
                }
            }
            foreach (var meshData in Meshes)
            {
                meshData.Box.Intersection(ref box);
            }

            if (test - _me.Position.Y < _currentGravityMomentum)
            {
                _currentGravityMomentum = 0;
            }
            else if (UpdateCamera != null)
            {
                UpdateCamera(this, new CameraEventArgs(_me));
            }

            _me.Position = box.Center;
        }

        public void TranslateLocal(Point3D point)
        {
            _me.TranslateLocal(point);

            if (!Collision) return;

            RunCollisions();
        }

        private void RunCollisions()
        {
            var box = _me.Box;
            foreach (var serface in Geometry)
            {
                foreach (var wall in serface.Walls)
                {
                    wall.Box.Intersection(ref box);
                }
            }
            foreach (var meshData in Meshes)
            {
                meshData.Box.Intersection(ref box);
            }

            foreach (var person in People)
            {
                person.Box.Intersection(ref box);
            }

            _me.Position = box.Center;
            if (UpdateCamera != null)
            {
                UpdateCamera(this, new CameraEventArgs(_me));
            }
        }

        public void RotateAzimuthLocal(Angle3D angle)
        {
            _me.RotateAzimuthLocal(angle);
            if (UpdateCamera != null)
            {
                UpdateCamera(this, new CameraEventArgs(_me));
            }
        }

        public void RotateAngleLocal(Angle3D angle)
        {
            _me.RotateAngleLocal(angle);
            if (UpdateCamera != null)
            {
                UpdateCamera(this, new CameraEventArgs(_me));
            }
        }

        public void OnUpdateCamera(CameraObject camera)
        {
            lock (People)
            {
                if (People.Any(c => c.ObjectGuid == camera.ObjectGuid))
                {
                    var person = People.First(c => c.ObjectGuid == camera.ObjectGuid);
                    person.Update(camera);
                }
                else if (camera.ObjectGuid != _me.ObjectGuid)
                {
                    People.Add(camera);
                }
            }
        }
    }

    public class CameraEventArgs : EventArgs
    {
        public CameraObject Camera;

        public CameraEventArgs(CameraObject camera)
        {
            Camera = camera;
        }
    }
}
